/*
 * MIT License
 *
 * Copyright (c) 2021 cao.yong<cymsn81@126.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.cy.websocket.stomp;

import org.cy.websocket.stomp.util.StringKit;

import java.io.Serializable;
import java.util.*;

/**
 * Created in 2021-05-08 22:24
 *
 * @author cao.yong
 */
public class StompHeaders implements Map<String, List<String>>, Serializable {

    // Standard headers (as defined in the spec)

    /**
     * SEND, MESSAGE, ERROR
     */
    public static final String CONTENT_TYPE = "content-type";

    /**
     * SEND, MESSAGE, ERROR
     */
    public static final String CONTENT_LENGTH = "content-length";

    /**
     * any client frame other than CONNECT
     */
    public static final String RECEIPT = "receipt";

    // CONNECT

    public static final String HOST = "host";

    public static final String ACCEPT_VERSION = "accept-version";

    public static final String LOGIN = "login";

    public static final String PASSCODE = "passcode";

    public static final String HEARTBEAT = "heart-beat";

    // CONNECTED

    public static final String SESSION = "session";

    public static final String SERVER = "server";

    public static final String VERSION = "version";

    // SEND

    public static final String DESTINATION = "destination";

    // SUBSCRIBE, UNSUBSCRIBE

    public static final String ID = "id";

    public static final String ACK = "ack";

    // MESSAGE

    public static final String SUBSCRIPTION = "subscription";

    public static final String MESSAGE_ID = "message-id";

    // RECEIPT

    public static final String RECEIPT_ID = "receipt-id";

    private final Map<String, List<String>> headers;

    public StompHeaders() {
        this(new LinkedHashMap<>(0), false);
    }

    public StompHeaders(Map<String, List<String>> headers, boolean readOnly) {
        if (readOnly) {
            Map<String, List<String>> map = new LinkedHashMap<>(headers.size());
            headers.forEach((key, value) -> map.put(key, Collections.unmodifiableList(value)));
            this.headers = Collections.unmodifiableMap(map);
        } else {
            this.headers = headers;
        }
    }

    public void setContentType(String contentType) {
        set(CONTENT_TYPE, contentType);
    }

    public String getContentType() {
        String value = getFirst(CONTENT_TYPE);
        return (value != null && !value.isEmpty()) ? value : null;
    }

    public void setContentLength(Integer contentLength) {
        set(CONTENT_LENGTH, Integer.toString(contentLength));
    }

    public Integer getContentLength() {
        String value = getFirst(CONTENT_LENGTH);
        return (value != null ? Integer.parseInt(value) : -1);
    }

    public void setReceipt(String receipt) {
        set(RECEIPT, receipt);
    }

    public String getReceipt() {
        return getFirst(RECEIPT);
    }

    public void setHost(String host) {
        set(HOST, host);
    }

    public String getHost() {
        return getFirst(HOST);
    }

    public void setAcceptVersion(String... acceptVersions) {
        if (null == acceptVersions) {
            set(ACCEPT_VERSION, null);
            return;
        }
        set(ACCEPT_VERSION, StringKit.join(",", acceptVersions));
    }

    public String[] getAcceptVersion() {
        String value = getFirst(ACCEPT_VERSION);
        return value != null ? value.split(",") : null;
    }

    public void setLogin(String login) {
        set(LOGIN, login);
    }

    public String getLogin() {
        return getFirst(LOGIN);
    }

    public void setPasscode(String passcode) {
        set(PASSCODE, passcode);
    }

    public String getPasscode() {
        return getFirst(PASSCODE);
    }

    /**
     * 配置心跳，以配置方为源对象设置
     * @param pingInterval 发送心跳间隔
     * @param pongInterval 接收心跳间隔
     */
    public void setHeartbeat(long pingInterval, long pongInterval) {
        String[] heartbeat = new String[]{Long.toString(pingInterval), Long.toString(pongInterval)};
        set(HEARTBEAT, StringKit.join(",", heartbeat));
    }

    public long[] getHeartbeat() {
        String rawValue = getFirst(HEARTBEAT);
        String[] rawValues = rawValue.split(",");
        if (rawValues.length == 0) {
            return null;
        }
        return new long[]{Long.parseLong(rawValues[0]), Long.parseLong(rawValues[1])};
    }

    public boolean isHeartbeatEnabled() {
        long[] heartbeat = getHeartbeat();
        return (heartbeat != null && heartbeat[0] != 0 && heartbeat[1] != 0);
    }

    public void setSession(String session) {
        set(SERVER, session);
    }

    public String getSession() {
        return getFirst(SESSION);
    }

    public void setServer(String server) {
        set(SERVER, server);
    }

    public String getServer() {
        return getFirst(SERVER);
    }

    public String getVersion() {
        return getFirst(VERSION);
    }

    public void setDestination(String destination) {
        set(DESTINATION, destination);
    }

    public String getDestination() {
        return getFirst(DESTINATION);
    }

    public void setId(String id) {
        set(ID, id);
    }

    public String getId() {
        return getFirst(ID);
    }

    public void setAck(String ack) {
        set(ACK, ack);
    }

    public String getAck() {
        return getFirst(ACK);
    }

    public void setSubscription(String subscription) {
        set(SUBSCRIPTION, subscription);
    }

    public String getSubscription() {
        return getFirst(SUBSCRIPTION);
    }

    public void setMessageId(String messageId) {
        set(MESSAGE_ID, messageId);
    }

    public String getMessageId() {
        return getFirst(MESSAGE_ID);
    }

    public void setReceiptId(String receiptId) {
        set(RECEIPT_ID, receiptId);
    }

    public String getReceiptId() {
        return getFirst(RECEIPT_ID);
    }

    public String getFirst(String headerName) {
        List<String> headerValues = this.headers.get(headerName);
        return headerValues != null ? headerValues.get(0) : null;
    }

    public void add(String headerName, String headerValue) {
        List<String> headerValues;
        if (this.headers.containsKey(headerName)) {
            headerValues = this.headers.get(headerName);
        } else {
            headerValues = new ArrayList<>(1);
            this.headers.put(headerName, headerValues);
        }
        headerValues.add(headerValue);
    }

    public void addAll(String headerName, List<? extends String> headerValues) {
        List<String> currentValues;
        if (this.headers.containsKey(headerName)) {
            currentValues = this.headers.get(headerName);
        } else {
            currentValues = new ArrayList<>(1);
            this.headers.put(headerName, currentValues);
        }
        currentValues.addAll(headerValues);
    }

    public void addAll(Map<String, List<String>> values) {
        for (Map.Entry<String, List<String>> elem: values.entrySet()) {
            addAll(elem.getKey(), elem.getValue());
        }
    }

    public void set(String headerName, String headerValue) {
        List<String> headerValues = new ArrayList<>(1);
        headerValues.add(headerValue);
        this.headers.put(headerName, headerValues);
    }

    public void setAll(String headerName, List<String> headerValues) {
        this.headers.put(headerName, headerValues);
    }

    public void setAll(Map<String, String> values) {
        for (Map.Entry<String, String> elem: values.entrySet()) {
            set(elem.getKey(), elem.getValue());
        }
    }

    public void mergeAll(Map<String, List<String>> values) {
        for (Map.Entry<String, List<String>> elem: values.entrySet()) {
            setAll(elem.getKey(), elem.getValue());
        }
    }

    public Map<String, String> toSingleValueMap() {
        LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<>(this.headers.size());
        for (Map.Entry<String, List<String>> elem : this.headers.entrySet()) {
            singleValueMap.put(elem.getKey(), elem.getValue().get(0));
        }
        return singleValueMap;
    }

    // Map Implementation

    @Override
    public int size() {
        return this.headers.size();
    }

    @Override
    public boolean isEmpty() {
        return this.headers.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return this.headers.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return this.headers.containsValue(value);
    }

    @Override
    public List<String> get(Object key) {
        return this.headers.get(key);
    }

    @Override
    public List<String> put(String key, List<String> value) {
        return this.headers.put(key, value);
    }

    @Override
    public List<String> remove(Object key) {
        return this.headers.remove(key);
    }

    @Override
    public void putAll(Map<? extends String, ? extends List<String>> map) {
        this.headers.putAll(map);
    }

    @Override
    public void clear() {
        this.headers.clear();
    }

    @Override
    public Set<String> keySet() {
        return this.headers.keySet();
    }

    @Override
    public Collection<List<String>> values() {
        return this.headers.values();
    }

    @Override
    public Set<Entry<String, List<String>>> entrySet() {
        return this.headers.entrySet();
    }

    @Override
    public int hashCode() {
        return this.headers.hashCode();
    }

    @Override
    public String toString() {
        return this.headers.toString();
    }

    public static StompHeaders readOnlyStompHeaders(Map<String, List<String>> headers) {
        return new StompHeaders((headers != null ? headers : Collections.emptyMap()), true);
    }
}
